package com.xishu.aop;

import com.alibaba.fastjson.JSONArray;
import com.xishu.config.Config;
import com.xishu.config.Constant;
import com.xishu.entity.AbstractEntity;
import com.xishu.entity.shop.TouchDevice;
import com.xishu.exception.ResponseStatusException;
import com.xishu.report.CacheReportService;
import com.xishu.response.ResponseData;
import com.xishu.response.ResponseStatus;
import com.xishu.response.VerifyUtil;
import com.xishu.service.CommonService;
import com.xishu.service.ShopService;
import com.xishu.util.*;
import org.apache.commons.lang.StringUtils;
import org.apache.logging.log4j.ThreadContext;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.List;

import static com.xishu.config.Constant.THREAD_LOCAL_VARIABLE_USER_ID;
import static com.xishu.response.ResponseStatus.SYSTEM_BUSY;

@Aspect
@Service
public class UserAop {
    private static Logger logger = LoggerFactory.getLogger(UserAop.class);

    @Pointcut("execution(public * com.xishu.controller.*.*(..))")
    public void webLog() {

    }

    private void printLog(ProceedingJoinPoint pjp) {
        if (pjp.getArgs() != null && pjp.getArgs().length != 0 && !(pjp.getArgs()[0] instanceof MultipartFile)) {
            try {
                if (logger.isInfoEnabled()) {
                    logger.info("option is {}  url is {}  REQUEST：{}, header:{} ",
                            ServletUtil.getRequest().getMethod(),
                            ServletUtil.getRequest().getRequestURL(),
                            requestToString(pjp),
                            ServletUtil.getAllHeaders()
                    );
                }
            } catch (Exception e) {
                logger.error("e", e);
            }
        }
    }

    //环绕通知,环绕增强，相当于MethodInterceptor
    @Around("webLog()")
    public Object arround(ProceedingJoinPoint pjp) throws Throwable {
        try {
            long startTime = System.currentTimeMillis();
            logger.info("aop start");
            String sessionId = ServletUtil.getSession().getId();
            ThreadContext.put(Constant.SESSION_ID, sessionId);
            ThreadContext.put(Constant.FE_REQUEST, "true");
            boolean requestMethodCheck = new Validator().duplicationRequestMethodCheck(((MethodSignature) pjp.getSignature()).getMethod(), sessionId, ServletUtil.getRequest().getRequestURI());

            //连续相同的请求
            if (!requestMethodCheck) {
                logger.info("will return empty response");
                return ResponseData.emptyResponse();
            }

            addVisitTime();
            Long userId = ServletUtil.getUserId();

            //添加用户ID，后续直接读线程变量，不用再到缓存里面读取
            if (userId != null) {
                ThreadContext.put(THREAD_LOCAL_VARIABLE_USER_ID, String.valueOf(userId));
            } else {
                ThreadContext.put(THREAD_LOCAL_VARIABLE_USER_ID, "");
            }

            ServletUtil.getResponse().setHeader(Constant.REQUEST_ID, ThreadContext.get(Constant.REQUEST_ID));
            addCrossDomain();

            if (userId != null) {
                ThreadContext.put(Constant.SESSION_USER_ID, String.valueOf(userId));
                //增加过期时间,30分钟
                ServletUtil.getSession().setMaxInactiveInterval(60 * 30);
            } else {
                //清空日志
                ThreadContext.put(Constant.SESSION_USER_ID, "");
            }

            CacheReportService.getInstance().onlineReport();

            //如果有请示过来了，并且有触摸屏设备ID，那么刷新缓存
            String touchDeviceUUID = ServletUtil.getSessionAttribute(Constant.TOUCH_DEVICE_REDIS_KEY);
            logger.info("touchDeviceUUID is {}", touchDeviceUUID);

            if (touchDeviceUUID != null) {
                //再延长
                JRedisUtil.expire(touchDeviceUUID, Config.getInstance().touchDeviceExpireTime());
                //先实时刷新
                List<TouchDevice> touchDeviceList = ShopService.getInstance().findTouchDevice(touchDeviceUUID);

                for (TouchDevice touchDevice : touchDeviceList) {
                    logger.info("will update touch device, {}", touchDevice);
                    touchDevice.setUpdateTime(System.currentTimeMillis());
                    touchDevice.setUserId(userId);
                    CommonService.getInstance().save(touchDevice);
                }
            }

            printLog(pjp);

            Object[] args = pjp.getArgs();

            if (Config.getInstance().performanceTest()) {
                args = Arrays.stream(args).map(p -> {
                    try {
                        return ClassUtil.clone(p);
                    } catch (Exception e) {
                        return null;
                    }
                }).filter(p -> p != null).toArray();
            }


            Signature signature = pjp.getSignature();
            MethodSignature methodSignature = (MethodSignature) signature;
            Method targetMethod = methodSignature.getMethod();
            Validator validator = new Validator();

            validator.checkAdminUser(targetMethod);

            if (pjp.getArgs() != null && pjp.getArgs().length != 0 && !(pjp.getArgs()[0] instanceof MultipartFile)) {
                validator.checkMethodValidator(targetMethod, pjp.getArgs()[0]);

                //如果是要操作对应的表，前台操作都不查询备份的对象
                if (pjp.getArgs()[0] instanceof AbstractEntity) {
                    AbstractEntity abstractEntity = (AbstractEntity) pjp.getArgs()[0];

                    //不是备份相关业务
                    if (abstractEntity.getEntity() == null) {
                        abstractEntity.setEntity(true);
                        abstractEntity.setVersion(null);
                        abstractEntity.setBackupTargetId(null);
                    }

                    abstractEntity.setBeRequest(false);

                    //外部查询，一定是要flagDelete为false的,如果是更新，删除，需要跟踪具体的业务来决定
                    abstractEntity.setFlagDelete(false);
                }
            }

            HttpServletResponse httpServletResponse = ServletUtil.getResponse();
            httpServletResponse.setDateHeader("Expires", 0);
            httpServletResponse.setHeader("Cache-Control", "no-cache");
            httpServletResponse.setHeader("Pragma", "no-cache");

            logger.info("pjp process begin");
            Object o = pjp.proceed();

            if (logger.isDebugEnabled()) {
                logger.debug("aop end and success, and response status is {}", o);
            } else {
                if (o instanceof ResponseData) {
                    ResponseData responseData = (ResponseData) o;
                    HttpServletRequest request = ServletUtil.getRequest();
                    if (Config.getInstance().performanceTest()) {
                        logger.info("method is {} , url is {} aop end and success, status is {}, cost time is {}, request data {}", ServletUtil.getRequest().getMethod(), request.getRequestURL(), responseData.getStatus(), System.currentTimeMillis() - startTime, new JSONArray(Arrays.asList(args)).toString());
                    } else {
                        logger.info("method is {} , url is {} aop end and success, status is {}, cost time is {}", ServletUtil.getRequest().getMethod(), request.getRequestURL(), responseData.getStatus(), System.currentTimeMillis() - startTime);
                    }

                } else {
                    logger.info("aop end and success, cost time is {}", System.currentTimeMillis() - startTime);
                }
            }

            return o;
        } catch (ResponseStatusException e) {
            logger.error("response exception", e);
            return new ResponseData(e.getResponseStatus());
        } catch (Exception e) {
            logger.error("Exception", e);
            return new ResponseData(ResponseStatus.SYSTEM_ERROR);
        } catch (Error error) {
            logger.error("Error", error);
            return new ResponseData(ResponseStatus.SYSTEM_ERROR);
        } finally {
            ThreadContext.remove(Constant.FE_REQUEST);
            ThreadContext.remove(Constant.THREAD_LOCAL_VARIABLE_USER_ID);
        }
    }

    /**
     * 添加访问次数
     *
     * @throws ResponseStatusException
     */
    private void addVisitTime() throws ResponseStatusException {
        if (!Config.getInstance().visitTimeOpen()) {
            return;
        }

        long startTime = System.currentTimeMillis();
        String remoteAddress = ServletUtil.getRemoteIp(ServletUtil.getRequest());

        //先判断是否已经有标记，说明该用户已经有潜在的攻击可能
        String kv = JRedisUtil.getKV(String.format("%s:%s", remoteAddress, Constant.IP_BUSY));
        logger.info("remote address {} add visit time {}", remoteAddress, kv);
        //说明已经存在攻击的可能性，不用再统计了
        if (kv != null) {
            VerifyUtil.verify(() -> Config.getInstance().closeSystemBusy(), SYSTEM_BUSY);
        }

        //添加访问次数的统计，如果一秒钟的访问次数超过了，那么该IP禁止访问30分钟
        String currentTime = DateUtil.format(System.currentTimeMillis(), "HH-mm-ss");
        long visitTime = JRedisUtil.incr(String.format("%s:%s", remoteAddress, currentTime));
        logger.info("visit time is {}", visitTime);

        //已经超了，那么添加标记，说明已经超了
        if (visitTime >= Config.getInstance().visitLimitTime()) {
            JRedisUtil.setKV(String.format("%s:%s", remoteAddress, Constant.IP_BUSY), System.currentTimeMillis());
            VerifyUtil.verify(() -> Config.getInstance().closeSystemBusy(), SYSTEM_BUSY);
        }

        logger.info("add visitTime, costTime {}", System.currentTimeMillis() - startTime);
    }

    private void addCrossDomain() {
        Config config = Config.getInstance();
        if (config.isSingleNode() || config.isLocalNode()) {
            logger.info("send cross domain");
            HttpServletRequest httpServletRequest = ServletUtil.getRequest();
            HttpServletResponse httpServletResponse = ServletUtil.getResponse();
            String originHeader = httpServletRequest.getHeader("Origin");
            if (StringUtils.isEmpty(originHeader)) originHeader = "*";
            httpServletResponse.setHeader("Access-Control-Allow-Origin", originHeader);
            httpServletResponse.setHeader("Access-Control-Allow-Origin", "*");
            httpServletResponse.setHeader("Access-Control-Allow-Methods", "GET,POST,PUT,DELETE,OPTIONS");
            httpServletResponse.setHeader("Access-Control-Allow-Credentials", "true");
            httpServletResponse.setHeader("Access-Control-Allow-Headers", "*");
        }
    }

    public String requestToString(ProceedingJoinPoint pjp) {
        try {
            return JSONArray.toJSONString(pjp.getArgs());
        } catch (Exception e) {
            //采用传统的方式
            try {
                return ServletUtil.requestParamToString(ServletUtil.getRequest());
            } catch (Exception e1) {
                logger.error("e", e1);
                return "";
            }
        }
    }
}
